#!/usr/bin/env python
#-*- coding: utf-8 -*-


'''
1,
    查询当前mmap, 如果没有admin退出

2，
    尽可能的对齐mmap到conf.meta
    如果当前的mmap大于conf.meta，则去掉admin之外多余的meta
    如果当前的mmap小于conf.meta，则按照集群内磁盘名字排列后，
    从第一个磁盘开始，依次选取为meta，直到满足conf个。

3,
    执行get_new_locs()

4,
    drop 任何节点上超过1个meta的meta, 第二步骤中被临时添加的meta优先被drop, admin不会被drop，

5,
    检查故障单元，从site级别开始，如果site数目超过3个，则检查单个故障单元不超过多数派。
    如果site数不超过3个，执行第六步。

    检查方法：从当前site中挑出最多meta数码的site，如果该site的meta数超过了多数派，那么
    就把他上面的一个meta删除

    重复该方法，直到site级别没有单节点故障发生。
    退出。

6，
    检查rack级别的故障单元：从当前rack中挑出最多meta数码的rack，如果该rack的meta数超过了多数派，那么
    就把他上面的一个meta删除

    重复该方法，直到site级别没有单节点故障发生。
    退出。
'''

import errno
import json
import re
import random
import time

from config import Config
from diskmap import DiskMap, name2map, map2name
from diskmap_utils import cluster_init, cluster_add_disk, cluster_del_disk, \
    disks_of_cluster, disks_of_site, disks_of_rack, disks_of_node,\
    get_nodes_of_gt_one_disk, \
    get_random_site, get_random_rack, get_random_node, get_random_disk
from metadata_balance_with_diskmap import get_new_locs
from storage import Storage
from utils import Exp, _derror, _dmsg, _exec_pipe, _str2dict
from etcd_manage import Etcd_manage

config = Config()
storage = Storage(config)

def get_lst(retry = 100):
    '''lst 格式如下:
        {
            'd2.r2.h3/0': 'admin',
            'd2.r2.h3/1': 'normal',
            'd2.r2.h3/2': 'normal',
            'd1.r1.h2/0': 'stopped',
            'd1.r1.h2/1': 'meta',
            'd1.r1.h2/2': 'deleting',
        }
    '''
    config = Config()
    storage = Storage(config)
    config.hosts_load()
    newlen = len(config.hosts)

    lst = {}
    i = 0
    while (i < retry):
        try:
            lst = storage.list_node(True)
        except Exp, e:
            if (e.errno ==  errno.EAGAIN):
                print(str(e))
                time.sleep(1)
                continue

        cluster = cluster_init(lst)
        if (cluster.node_count() < newlen):
            print("get_lst, retry_max: %s, retry: %s" % (retry, i))
            time.sleep(1)
            i = i + 1
        else:
            break

    return lst

def get_lst_v4():
    lst = get_lst()
    lst_new = {}
    for k in lst.keys():
        k_new = "%s/0" % k
        lst_new.update({k_new: lst[k]})
    return lst_new


def get_admin(lst):
    #return [site, rack, node, disk] or None
    admin = None
    admin_fake = None

    for (k, v) in lst.items():
        if (v == 'stopped' or 'deleting' in v):
            continue

        admin_fake = name2map(k)
        if ("admin" in v):
            admin = name2map(k)

    if admin is None:
        admin = admin_fake

    return admin

def meta_align(metas, loc_master, cluster):
    metas = [map2name(x) for x in metas]

    config = Config()
    if len(metas) > config.meta:
        metas = list(set(metas) - set([loc_master]))
        metas.insert(0, loc_master)
        metas = metas[:config.meta]
    else:
        disks = disks_of_cluster(cluster)
        disks = sorted([map2name(x) for x in disks])

        for disk in disks:
            if len(metas) >= config.meta:
                break

            if disk not in set(metas):
                metas.append(disk)

    metas = [name2map(x) for x in metas]
    return metas

def drop_more_meta(mmap, metas_origin, loc_master):
    from cluster import Cluster
    cluster = Cluster()
    if cluster.is_standalone(force=True):
        return 

    while True:
        nodes_more = []
        for site_ent in mmap.site.child:
            for rack_ent in site_ent.child:
                nodes = get_nodes_of_gt_one_disk(mmap, site_ent.name, rack_ent.name)
                nodes_more += nodes

        if not nodes_more:
            break

        x = nodes_more[0]
        disks = disks_of_node(mmap, x[0], x[1], x[2])
        disks = [map2name(x) for x in disks]
        disks_origin = [map2name(x) for x in metas_origin]
        disks_origin = [x for x in disks_origin if x in disks]

        disks1 = list(set(disks) - set(disks_origin))
        disks2 = list(set(disks) - set(disks1) - set([loc_master]))
        disks = disks1 + disks2
        disk = name2map(disks[0])

        cluster_del_disk(mmap, disk[0], disk[1], disk[2], disk[3])

def balance_meta_in_site(mmap, cluster, metas_origin, loc_master):
    while True:
        sites = []
        for site_ent in mmap.site.child:
            sites.append(site_ent.id)

        sites_cluster = []
        for site_ent in cluster.site.child:
            sites_cluster.append(site_ent.id)

        sites = sorted(
                sites,
                cmp=lambda x,y:cmp(
                    len(disks_of_site(mmap, x)), 
                    len(disks_of_site(mmap, y)), 
                    ), 
                reverse=True)

        site_max = sites[0]
        site_min = sites[-1]

        if len(disks_of_site(mmap, site_max)) < (mmap.disk_count() + 1)/2:
            break

        disks_all_site_min = [map2name(x) for x in disks_of_site(cluster, site_min)]
        disks_used_site_min = [map2name(x) for x in disks_of_site(mmap, site_min)]
        disks_not_use_site_min = list(set(disks_all_site_min) - set(disks_used_site_min))

        disks = disks_of_site(mmap, site_max)
        disks = [map2name(x) for x in disks]
        disks_origin = [map2name(x) for x in metas_origin]
        disks_origin = [x for x in disks_origin if x in disks]

        disks1 = list(set(disks) - set(disks_origin))
        disks2 = list(set(disks_origin) - set([loc_master]))
        disks = disks1 + disks2
        disk = name2map(disks[0])

        cluster_del_disk(mmap, disk[0], disk[1], disk[2], disk[3])

def balance_meta_in_rack(mmap, cluster, metas_origin, loc_master):
    while True:
        racks = []
        for site_ent in mmap.site.child:
            for rack_ent in site_ent.child:
                racks.append((site_ent.name, rack_ent.name))

        racks_cluster = []
        for site_ent in cluster.site.child:
            for rack_ent in site_ent.child:
                racks_cluster.append((site_ent.name, rack_ent.name))

        racks = sorted(
                racks,
                cmp=lambda x,y:cmp(
                    len(disks_of_rack(mmap, x[0], x[1])), 
                    len(disks_of_rack(mmap, y[0], y[1])), 
                    ),
                reverse=True)

        rack_max = racks[0]
        rack_min = racks[-1]
        if len(disks_of_rack(mmap, rack_max[0], rack_max[1])) < (mmap.disk_count() + 1)/2:
            break

        disks_all_rack_min = [map2name(x) for x in disks_of_rack(cluster, rack_min[0], rack_min[1])]
        disks_used_rack_min = [map2name(x) for x in disks_of_rack(mmap, rack_min[0], rack_min[1])]
        disks_not_use_rack_min = list(set(disks_all_rack_min) - set(disks_used_rack_min))

        disks = disks_of_rack(mmap, rack_max[0], rack_max[1])
        disks = [map2name(x) for x in disks]
        disks_origin = [map2name(x) for x in metas_origin]
        disks_origin = [x for x in disks_origin if x in disks]

        disks1 = list(set(disks) - set(disks_origin))
        disks2 = list(set(disks_origin) - set([loc_master]))
        disks = disks1 + disks2
        disk = name2map(disks[0])

        cluster_del_disk(mmap, disk[0], disk[1], disk[2], disk[3])

def get_new_metas(lst, loc_master):
    #loc_master = 'd1.r1.h1/0'
    lst_meta = {}
    [lst_meta.update({k: v}) for (k, v) in lst.items() if ("admin" in v or  "meta" in v)] 
    
    cluster = cluster_init(lst)
    mmap = cluster_init(lst_meta)

    metas_origin = disks_of_cluster(mmap)
    metas_after_align = meta_align(metas_origin, loc_master, cluster)

    locs = [map2name(x) for x in metas_after_align]
    metas_new = get_new_locs(lst, locs, loc_master)
    _metas_new = {}
    [_metas_new.update({x: 'normal'}) for x in metas_new]
    mmap = cluster_init(_metas_new)

    drop_more_meta(mmap, metas_origin, loc_master)

    if cluster.site_count() >= 3:
        balance_meta_in_site(mmap, cluster, metas_origin, loc_master)
    else:
        if cluster.rack_count() >= 3:
            balance_meta_in_rack(mmap, cluster, metas_origin, loc_master)

    disks = disks_of_cluster(mmap)
    metas_new = [map2name(x) for x in disks]
    metas_origin = [map2name(x) for x in metas_origin]
    return (metas_new, metas_origin)

def dropmeta(x, storage_cls=None):
    if storage_cls is None:
        config = Config()
        storage_cls = Storage(config)

    retry = 0
    retry_max = 10

    while (1):
        try:
            storage_cls.dropmeta(x.split('/')[0], True)
            break
        except Exp, e:
            if (e.errno in [errno.EAGAIN, errno.ENONET, errno.EBUSY]):
                retry = retry + 1
                if (retry > retry_max):
                    _derror("retry fail, too more")
                    raise Exp(e.errno, "retry fail")

                time.sleep(1)
                continue
            elif (e.errno in [errno.ENOENT]):
                break
            else:
                raise Exp(e.errno, e.err)

def setmeta(x, storage_cls=None):
    if storage_cls is None:
        config = Config()
        storage_cls = Storage(config)

    retry = 0
    retry_max = 10

    while (1):
        try:
            storage_cls.setmeta(x.split('/')[0], True)
            break
        except Exp, e:
            if (e.errno in [errno.EAGAIN, errno.ENONET, errno.EBUSY]):
                retry = retry + 1
                if (retry > retry_max):
                    _derror("retry fail, too more")
                    raise Exp(e.errno, "retry fail")

                time.sleep(1)
                continue
            elif (e.errno in [errno.EEXIST]):
                break
            else:
                raise Exp(e.errno, e.err)

def get_normals(lst):
    normals = []

    for node in lst:
        if lst[node] == 'normal':
            normals.append(node)

    return normals

def drop_stopped_meta(lst, storage_cls, skips=None):
    admin_fake = map2name(get_admin(lst))
    admin = admin_fake.split('/')[0]
    metas = []
    res = _exec_pipe([config.admin, '--stat', admin], 3, False)[:-1]
    if (len(res) != 0):
        d = _str2dict(res)
        if (d.has_key('metas')):
            metas = d['metas'].strip(",").split(",")

    normals = get_normals(lst)
    for m in metas:
        if (m not in lst.keys()):
            if len(normals):
                print("drop metas not in lst, may be stopped: %s, lst; %s" % (str(metas), str(lst)))
                dropmeta(m, storage_cls)
                normals.pop()
        else:
            v = lst[m]
            if ( v == 'stopped' or 'deleting' in v): 
                if (skips is not None) and (m in skips):
                    continue
                if len(normals):
                    print("drop metas that was stopped: %s, lst; %s" % (str(metas), str(lst)))
                    dropmeta(m, storage_cls)
                    normals.pop()


def _balance(need_sets, need_drops, metas_origin, storage_cls):
    meta_max = 9
    meta_min = 2

    if (len(need_sets) == 0 and len(need_drops) == 0):
        return

    if len(metas_origin) < meta_max:
        if need_sets:
            x = need_sets[0]
            setmeta(x, storage_cls)
            need_sets.pop(0)
            metas_origin.append(x)

        if need_drops:
            x = need_drops[0]
            dropmeta(need_drops[0], storage_cls)
            need_drops.pop(0)
            metas_origin = list(set(metas_origin) - set([x]))
    else:
        if need_drops:
            x = need_drops[0]
            dropmeta(x, storage_cls)
            need_drops.pop(0)
            metas_origin = list(set(metas_origin) - set([x]))

    _balance(need_sets, need_drops, metas_origin, storage_cls)


def balance(lst=None, storage_cls=None):
    print "balance disabled"
    return

    etcd_metalist = []
    etcd_manage = Etcd_manage()

    if storage_cls is None:
        config = Config()
        storage_cls = Storage(config)

    print("metanode balance..")
    if lst is None:
        lst = storage_cls.list_node(True)

    print("lst:", lst)

    drop_stopped_meta(lst, storage_cls, skips=None)

    admin = map2name(get_admin(lst))
    (metas_new, metas_origin) = get_new_metas(lst, admin)

    need_sets = list(set(metas_new) - set(metas_origin))
    need_drops = list(set(metas_origin) - set(metas_new))
    _balance(need_sets, need_drops, metas_origin, storage_cls)

    print('new:', metas_new, 'old:', metas_origin, 'admin:', admin)
    for meta in metas_new:
        host = meta.split('/')[0]
        etcd_metalist.append(host)

    etcd_manage.etcd_member_update(etcd_metalist)

    return (metas_new, metas_origin, admin)


class FakeStorage():
    def setmeta(self, disk, xx):
        print("-----set %s meta" % (disk))

    def dropmeta(self, disk, xx):
        print("-----drop %s meta" % (disk))


def test_cases():
    cases = {
        'single_node1': {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",
            "d1.r1.h1/3" : "normal",
            "d1.r1.h1/4" : "normal",
         },

        'single_node2': {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",
            "d1.r1.h1/3" : "normal",
            "d1.r1.h1/4" : "normal",
            "d1.r1.h1/5" : "normal",
            "d1.r1.h1/6" : "normal",
            "d1.r1.h1/7" : "normal",
            "d1.r1.h1/8" : "normal",
            "d1.r1.h1/9" : "normal",
            "d1.r1.h1/10" : "normal",
            "d1.r1.h1/11" : "normal",
        },

        'single_rack_2node': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "admin",
            "d1.r1.h1/3" : "normal",
            "d1.r1.h1/4" : "normal",
            "d1.r1.h2/5" : "normal",
            "d1.r1.h2/6" : "normal",
            "d1.r1.h2/7" : "normal",
            "d1.r1.h2/8" : "meta",
            "d1.r1.h2/9" : "normal",
            "d1.r1.h2/10" : "normal",
            "d1.r1.h2/11" : "normal",
        },

        'single_rack_3node': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h2/2" : "normal",
            "d1.r1.h3/0" : "normal",
            "d1.r1.h3/1" : "normal",
            "d1.r1.h3/2" : "normal",
        },

        '2site_2rack_3node': {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",

            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h2/2" : "normal",

            "d2.r2.h3/0" : "normal",
            "d2.r2.h3/1" : "normal",
            "d2.r2.h3/2" : "normal",
        },

        'single_rack_3node_2disk': {
            'site1.rack1.node185/0': 'meta',
            'site1.rack1.node185/1': 'normal',

            'site1.rack1.node186/0': 'normal',
            'site1.rack1.node186/1': 'normal',

            'site1.rack1.node184/0': 'admin',
            'site1.rack1.node184/1': 'normal',
        },

        'single_rack_4node': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",
            "d1.r1.h1/3" : "normal",
            "d1.r1.h1/4" : "normal",
            "d1.r1.h2/5" : "normal",
            "d1.r1.h2/6" : "normal",
            "d1.r1.h3/7" : "normal",
            "d1.r1.h3/8" : "normal",
            "d1.r1.h4/9" : "normal",
            "d1.r1.h4/10" : "normal",
            "d1.r1.h4/11" : "normal",
        },

        'single_rack_5node': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/2" : "normal",
            "d1.r1.h1/3" : "normal",
            "d1.r1.h1/4" : "normal",
            "d1.r1.h2/5" : "normal",
            "d1.r1.h2/6" : "normal",
            "d1.r1.h3/7" : "normal",
            "d1.r1.h3/8" : "normal",
            "d1.r1.h4/9" : "normal",
            "d1.r1.h4/10" : "normal",
            "d1.r1.h5/11" : "normal",
        },

        '2rack': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h3/0" : "normal",
            "d1.r1.h3/1" : "normal",
    
            "d1.r2.h1/0" : "normal",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "normal",
            "d1.r2.h2/1" : "normal",
            "d1.r2.h3/0" : "normal",
            "d1.r2.h3/1" : "normal",
        },

        '3rack': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h3/0" : "normal",
            "d1.r1.h3/1" : "normal",
    
            "d1.r2.h1/0" : "normal",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "normal",
            "d1.r2.h2/1" : "normal",
            "d1.r2.h3/0" : "normal",
            "d1.r2.h3/1" : "normal",
    
            "d1.r3.h1/0" : "normal",
            "d1.r3.h1/1" : "normal",
            "d1.r3.h2/0" : "normal",
            "d1.r3.h2/1" : "normal",
            "d1.r3.h3/0" : "normal",
            "d1.r3.h3/1" : "normal",
        },

        "9rack": {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
    
            "d1.r2.h1/0" : "normal",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "meta",
            "d1.r2.h2/1" : "normal",
    
            "d1.r3.h1/0" : "normal",
            "d1.r3.h1/1" : "meta",
            "d1.r3.h2/0" : "normal",
            "d1.r3.h2/1" : "normal",
    
            "d1.r4.h1/0" : "normal",
            "d1.r4.h1/1" : "normal",
            "d1.r4.h2/0" : "normal",
            "d1.r4.h2/1" : "normal",
    
            "d1.r5.h1/0" : "normal",
            "d1.r5.h1/1" : "normal",
            "d1.r5.h2/0" : "meta",
            "d1.r5.h2/1" : "normal",
    
            "d1.r6.h1/0" : "normal",
            "d1.r6.h1/1" : "normal",
            "d1.r6.h2/0" : "normal",
            "d1.r6.h2/1" : "normal",
    
            "d1.r7.h1/0" : "normal",
            "d1.r7.h1/1" : "normal",
            "d1.r7.h2/0" : "meta",
            "d1.r7.h2/1" : "normal",
    
            "d1.r8.h1/0" : "normal",
            "d1.r8.h1/1" : "meta",
            "d1.r8.h2/0" : "normal",
            "d1.r8.h2/1" : "normal",
    
            "d1.r9.h1/0" : "normal",
            "d1.r9.h1/1" : "normal",
            "d1.r9.h2/0" : "normal",
            "d1.r9.h2/1" : "meta",
        },
        "9rack_with_2meta": {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
    
            "d1.r2.h1/0" : "meta",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "normal",
            "d1.r2.h2/1" : "normal",
    
            "d1.r3.h1/0" : "normal",
            "d1.r3.h1/1" : "normal",
            "d1.r3.h2/0" : "normal",
            "d1.r3.h2/1" : "normal",
    
            "d1.r4.h1/0" : "normal",
            "d1.r4.h1/1" : "normal",
            "d1.r4.h2/0" : "normal",
            "d1.r4.h2/1" : "normal",
    
            "d1.r5.h1/0" : "normal",
            "d1.r5.h1/1" : "normal",
            "d1.r5.h2/0" : "normal",
            "d1.r5.h2/1" : "normal",
    
            "d1.r6.h1/0" : "normal",
            "d1.r6.h1/1" : "normal",
            "d1.r6.h2/0" : "normal",
            "d1.r6.h2/1" : "normal",
    
            "d1.r7.h1/0" : "normal",
            "d1.r7.h1/1" : "normal",
            "d1.r7.h2/0" : "normal",
            "d1.r7.h2/1" : "normal",
    
            "d1.r8.h1/0" : "normal",
            "d1.r8.h1/1" : "normal",
            "d1.r8.h2/0" : "normal",
            "d1.r8.h2/1" : "normal",
    
            "d1.r9.h1/0" : "normal",
            "d1.r9.h1/1" : "normal",
            "d1.r9.h2/0" : "normal",
            "d1.r9.h2/1" : "normal",
        },

        "2site_2rack_3node": {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/3" : "normal",
    
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h2/2" : "normal",
    
            "d2.r2.h3/0" : "admin",
            "d2.r2.h3/1" : "normal",
            "d2.r2.h3/2" : "normal",
        },

        "1site_3rack_7node": {
            'datacenter1.rack1.node11/0': 'admin',
            'datacenter1.rack1.node11/1': 'normal',

            'datacenter1.rack1.node12/0': 'normal',
            'datacenter1.rack1.node12/1': 'normal',
            'datacenter1.rack1.node12/2': 'normal',

            'datacenter1.rack1.node16/0': 'normal',
            'datacenter1.rack1.node16/1': 'normal',
            'datacenter1.rack1.node16/2': 'normal',
            'datacenter1.rack1.node16/3': 'normal',

            'datacenter1.rack2.node14/0': 'normal',
            'datacenter1.rack2.node14/1': 'meta',
            'datacenter1.rack2.node14/2': 'normal',

            'datacenter1.rack2.node13/0': 'normal',
            'datacenter1.rack2.node13/1': 'normal',
            'datacenter1.rack2.node13/2': 'normal',

            'datacenter1.rack2.node17/0': 'normal',
            'datacenter1.rack2.node17/1': 'normal',
            'datacenter1.rack2.node17/2': 'normal',
            'datacenter1.rack2.node17/3': 'normal',

            'datacenter1.rack3.node19/0': 'meta',
        },

        '2site_2rack_4node': {
            "d1.r1.h1/0" : "normal",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h1/3" : "normal",
    
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
            "d1.r1.h2/2" : "normal",
    
            "d2.r2.h3/0" : "admin",
            "d2.r2.h3/1" : "normal",
            "d2.r2.h3/2" : "normal",
    
            "d2.r2.h4/0" : "normal",
            "d2.r2.h4/1" : "normal",
            "d2.r2.h4/2" : "normal",
        },

        "1site_3rack_7ndoe": {
            'datacenter1.rack1.node11/0': 'normal',
            'datacenter1.rack1.node11/1': 'normal',

            'datacenter1.rack1.node12/0': 'normal',
            'datacenter1.rack1.node12/1': 'admin',
            'datacenter1.rack1.node12/2': 'normal',

            'datacenter1.rack1.node16/0': 'meta',
            'datacenter1.rack1.node16/1': 'normal',
            'datacenter1.rack1.node16/2': 'normal',
            'datacenter1.rack1.node16/3': 'normal',

            'datacenter1.rack2.node13/0': 'meta',
            'datacenter1.rack2.node13/1': 'normal',
            'datacenter1.rack2.node13/2': 'normal',

            'datacenter1.rack2.node14/0': 'normal',
            'datacenter1.rack2.node14/1': 'normal',
            'datacenter1.rack2.node14/2': 'normal',

            'datacenter1.rack2.node17/0': 'normal',
            'datacenter1.rack2.node17/1': 'meta',
            'datacenter1.rack2.node17/2': 'normal',
            'datacenter1.rack2.node17/3': 'normal',

            'datacenter1.rack3.node15/0': 'normal',
            'datacenter1.rack3.node15/1': 'meta',

            'datacenter1.rack3.node19/0': 'meta',
        },

        "4site_9rack": {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
    
            "d1.r2.h1/0" : "normal",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "normal",
            "d1.r2.h2/1" : "normal",
    
            "d2.r3.h1/0" : "normal",
            "d2.r3.h1/1" : "normal",
            "d2.r3.h2/0" : "normal",
            "d2.r3.h2/1" : "normal",
    
            "d2.r4.h1/0" : "normal",
            "d2.r4.h1/1" : "normal",
            "d2.r4.h2/0" : "normal",
            "d2.r4.h2/1" : "normal",
    
            "d3.r5.h1/0" : "normal",
            "d3.r5.h1/1" : "normal",
            "d3.r5.h2/0" : "normal",
            "d3.r5.h2/1" : "normal",
    
            "d3.r6.h1/0" : "normal",
            "d3.r6.h1/1" : "normal",
            "d3.r6.h2/0" : "normal",
            "d3.r6.h2/1" : "normal",
    
            "d4.r7.h1/0" : "normal",
            "d4.r7.h1/1" : "normal",
            "d4.r7.h2/0" : "normal",
            "d4.r7.h2/1" : "normal",
    
            "d4.r8.h1/0" : "normal",
            "d4.r8.h1/1" : "normal",
            "d4.r8.h2/0" : "normal",
            "d4.r8.h2/1" : "normal",
    
            "d4.r9.h1/0" : "normal",
            "d4.r9.h1/1" : "normal",
            "d4.r9.h2/0" : "normal",
            "d4.r9.h2/1" : "normal",
        },
        "4site_9rack_with_7meta": {
            "d1.r1.h1/0" : "admin",
            "d1.r1.h1/1" : "normal",
            "d1.r1.h2/0" : "normal",
            "d1.r1.h2/1" : "normal",
    
            "d1.r2.h1/0" : "normal",
            "d1.r2.h1/1" : "normal",
            "d1.r2.h2/0" : "meta",
            "d1.r2.h2/1" : "normal",
    
            "d2.r3.h1/0" : "normal",
            "d2.r3.h1/1" : "normal",
            "d2.r3.h2/0" : "meta",
            "d2.r3.h2/1" : "normal",
    
            "d2.r4.h1/0" : "normal",
            "d2.r4.h1/1" : "normal",
            "d2.r4.h2/0" : "meta",
            "d2.r4.h2/1" : "normal",
    
            "d3.r5.h1/0" : "normal",
            "d3.r5.h1/1" : "normal",
            "d3.r5.h2/0" : "normal",
            "d3.r5.h2/1" : "normal",
    
            "d3.r6.h1/0" : "normal",
            "d3.r6.h1/1" : "normal",
            "d3.r6.h2/0" : "meta",
            "d3.r6.h2/1" : "normal",
    
            "d4.r7.h1/0" : "normal",
            "d4.r7.h1/1" : "meta",
            "d4.r7.h2/0" : "normal",
            "d4.r7.h2/1" : "normal",
    
            "d4.r8.h1/0" : "normal",
            "d4.r8.h1/1" : "normal",
            "d4.r8.h2/0" : "meta",
            "d4.r8.h2/1" : "normal",
    
            "d4.r9.h1/0" : "normal",
            "d4.r9.h1/1" : "normal",
            "d4.r9.h2/0" : "normal",
            "d4.r9.h2/1" : "normal",
        },
    "2site_3rack_with_4node": {
            'site2.rack1.node97/0': 'meta',
            'site1.rack1.node37/0': 'meta',
            'site1.rack1.node37/1': 'normal',
            'site1.rack2.node99/0': 'normal',
            'site1.rack2.node99/1': 'normal',
            'site1.rack1.node35/0': 'admin',
            'site1.rack1.node35/1': 'normal',
            'site1.rack1.node53/0': 'meta',
            'site1.rack1.node53/1': 'normal',
        },
    "1site_3rack_with_4node": {
            'st1.rk1.node18/0': 'admin',
            'st1.rk1.node18/1': 'normal',
            'st1.rk1.node18/2': 'normal',

            'st1.rk1.node20/0': 'normal',
            'st1.rk1.node20/1': 'normal',

            'st1.rk2.node22/0': 'normal',
            'st1.rk2.node22/1': 'normal',

            'st1.rk3.node24/0': 'normal',
            'st1.rk3.node24/1': 'normal',
        },

    }

    return cases

def test():
    #lst = get_lst()
    storage = FakeStorage()
    cases = test_cases()
    print("= ="*50)
    for (k, v) in cases.items():
        print("*"*20)
        print('case:', k)
        (metas_new, metas_origin, admin) = balance(v, storage)
        print('admin:', admin)
        print('origin:', sorted(metas_origin))
        print('new:', sorted(metas_new))
    print('-'*50)
    print("all_cases: ", cases.keys())
    print('-'*10)

if __name__ == "__main__":
    balance()

